"""
交叉特征： 给离散特征做笛卡尔积 形成高维的离散特征
wide deep 模型：
1.wide的输入就是要做交叉特征 这样可以使wide模型记住各种特征组合 提高记忆力 但是同时泛化能力变弱了 因为当测试集中出现了
没有见过的特征组合时 比如出现一个(age:150,sex:man)的特征组合 wide模型就很难预测
2.deep模型主要是通过神经网络学习特征(离散连续都可以)中的一些抽象信息 提高泛化能力 对于没有出现过的特征组合也有比较好的预测 
3.因此交叉特征适用于wide这一类LR的线性模型 对deep模型使用交叉特征就会降低准确率

"""

import pandas as pd 
import tensorflow as tf
from tensorflow import keras 
import os

# 准备数据集 
train_file = './tf_estimator/titanic/train.csv'
eval_file = './tf_estimator/titanic/eval.csv'
train_df = pd.read_csv(train_file)
eval_df = pd.read_csv(eval_file)
train_y = train_df.pop('survived')  
eval_y = eval_df.pop('survived')

def make_datasets(train_df,train_y,epochs=10,shuffle=True,batch_size=32):
      datasets = tf.data.Dataset.from_tensor_slices((dict(train_df),train_y)) 
      if shuffle:
            datasets = datasets.shuffle(10000)
      datasets = datasets.repeat(epochs).batch(batch_size)
      return datasets

train_datasets = make_datasets(train_df,train_y)
eval_datasets = make_datasets(eval_df,eval_y)


# 定义特征工程方式 
categorical_features = ['sex','n_siblings_spouses','parch','class','deck','embark_town','alone'] 
numeric_features = ['age','fare']  
feature_columns = []  

for categorical_feature in categorical_features:
      vocab = train_df[categori_feature].unique()  
      feature_columns.append(  
        tf.feature_column.categorical_column_with_vocabulary_list(categorical_feature,vocab)
      )  

for numeric_feature in numeric_features:
      feature_columns.append(
        tf.feature_column.numeric_column(numeric_feature,dtype=tf.float32)
      )  

# 交叉特征
"""
corss_feature ： 假设age有100个 sex有100两个 他们交叉出来的特征就有10,000个 
因为age和sex本身就是一个维度(100,)的onehot编码的离散特征 所以这10,000就会非常的稀疏 
因此对这10，000个新特征做哈希运算变成10,000个hash后的值 

hash_bucket_size : 笛卡尔积常导致交叉特征过多 因此要减少特征量 如交叉出来是10000个特征 然后分为
100类  加上原来的就总共只有300个特征 
"""
feature_columns.append(
    tf.feature_column.indicator_column(  # dnn要转换成indicator_column才能识别 lr转不转换都可以
        tf.feature_column.crossed_column(['age','sex'],hash_bucket_size=100)
    )
)

# LinearClassifier
linear_output_file = './tf_estimator/linear_estimator_cross_feature'  
if not os.path.exists(linear_output_file):
    os.mkdir(linear_output_file)

linear_estimator = tf.estimator.LinearClassifier(
    model_dir = linear_output_file,n_classes=2,feature_columns=feature_columns
    )
linear_estimator.train(input_fn=lambda  : make_datasets(train_df,train_y,epochs=10))
linear_estimator.evaluate(input_fn=lambda  : make_datasets(eval_df,eval_y,epochs=1))